#include "nand_flash.h"

#include "s3c2440.h"
#include "string_utils.h"

#define NAND_PAGE_SIZE 512
#define NAND_SPARE_SIZE 16
#define NAND_BLOCK_SIZE (32 * NAND_PAGE_SIZE)

#define CMD_READ1 0x00
#define CMD_READ2 0x30
#define CMD_WRITE1 0x80
#define CMD_WRITE2 0x10
#define CMD_ERASE1 0x60
#define CMD_ERASE2 0xD0
#define CMD_STATUS 0x70
#define CMD_RESET 0xFF

void nand_init(void) {
#define TALCS 0
#define TWRPH0 1
#define TWRPH1 0
    NFCONF = (TALCS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4);
    NFCONT = (1 << 4) | (1 << 1) | (1 << 0);
}

void nand_select() {
    /* 使能片选 */
    NFCONT &= ~(1 << 1);
}

void nand_deselect(void) {
    /* 禁止片选 */
    NFCONT |= (1 << 1);
}

void nand_cmd(uint8 cmd) {
    volatile int i;
    NFCCMD = cmd;
}

static void nand_send_addr(uint32 col, uint32 page) {
    nand_cmd(col & 0xFF);                    // Column A0-A7
    nand_cmd((col >> 8) | ((page & 0xFF)));  // Column A8 + Page A0-A7
    nand_cmd((page >> 8) & 0xFF);            // Page A8-A15
    nand_cmd((page >> 16) & 0x01);           // Page A16
}

void nand_addr_byte(uint8 addr) {
    volatile int i;
    NFADDR = addr;
    for (i = 0; i < 10; i++);
}

uint8 nand_data(void) { return NFDATA; }

void nand_w_data(unsigned char val) {
    volatile int i;
    NFDATA = val;
    for (i = 0; i < 10; i++);
}

static void wait_ready(void) {
    int i = 0;
    while (!(NFSTAT & 1));
    for (i = 0; i < 10; i++);
}

int nand_check_bad_block(uint32 block_addr) {
    uint32 page_addr = block_addr * 32;  // 每块32页
    uint8 marker;

    // 读取Spare Area第6字节（坏块标记）
    nand_cmd(CMD_READ1);
    uint32 addr = page_addr * NAND_PAGE_SIZE + NAND_PAGE_SIZE;
    int page = addr / NAND_PAGE_SIZE;
    int col = addr & (NAND_PAGE_SIZE - 1);
    nand_send_addr(col, page);  // 指向Spare区
    nand_cmd(CMD_READ2);
    wait_ready();

    marker = NFDATA;  // 实际需读取到对应偏移量
    printf("marker:0x%x\r\n", marker);
    return (marker != 0xFF);
}

void nand_read(uint32 addr, uint8 *buf, uint32 len) {
    int i = 0;
    int page = addr / NAND_PAGE_SIZE;
    int col = addr & (NAND_PAGE_SIZE - 1);

    printf("read page:0x%x, col:0x%x\r\n", page, col);
    nand_select();
    while (i < len) {
        // 发送00h命令
        if (col < 255) {
            nand_cmd(00);
        } else {
            nand_cmd(01);
        }

        nand_cmd(col & 0xFF);                    // Column A0-A7
        nand_cmd((page & 0xFF));                 // Page A0-A7
        nand_cmd((page >> 8) & 0xFF);            // Page A8-A15
        nand_cmd((page >> 16) & 0x01);           // Page A16

        // 发出30h命令
        nand_cmd(0x30);

        // 等待就绪
        wait_ready();

        // 读数据
        if (col < 255) {
            for (; (col < 255) && (i < len); col++) {
                buf[i++] = nand_data();
            }
        } else {
            for (; (col < 512) && (i < len); col++) {
                buf[i++] = nand_data();
            }
        }

        if (i == len) {
            break;
        }
        col = 0;
        page++;
    }

    nand_deselect();
}

void do_read_nand_flash(void) {
    unsigned int addr;
    volatile unsigned char *p;
    int i, j;
    unsigned char c;
    unsigned char str[16];
    unsigned char buf[64];

    /* 获得地址 */
    printf("Enter the address to read: ");
    addr = get_uint();

    nand_read(addr, buf, 64);
    p = (volatile unsigned char *)buf;

    printf("Data : \n\r");
    /* 长度固定为64 */
    for (i = 0; i < 4; i++) {
        /* 每行打印16个数据 */
        for (j = 0; j < 16; j++) {
            /* 先打印数值 */
            c = *p++;
            str[j] = c;
            printf("%02x ", c);
        }

        printf("   ; ");

        for (j = 0; j < 16; j++) {
            /* 后打印字符 */
            if (str[j] < 0x20 || str[j] > 0x7e) /* 不可视字符 */
                putchar('.');
            else
                putchar(str[j]);
        }
        printf("\n\r");
    }
}

int nand_erase(uint32 addr, int len) {
    int page = addr / 0x3FFF;
    nand_select();
    // if (addr & (0x3FFF)) {
    //     printf("nand_erase addr is not block align\r\n");
    //     return -1;
    // }

    // if (len & (0x3FFF)) {
    //     printf("nand_erase len is not block align\r\n");
    //     return -1;
    // }

    while (1) {
        page = addr & 0x3FFF;
        nand_cmd(0x60);

        nand_addr_byte(page & 0xff);
        nand_addr_byte((page >> 8) & 0xff);
        nand_addr_byte((page >> 16) & 0xff);

        nand_cmd(0xD0);
        wait_ready();
        len -= (16 * 1024);
        if (len <= 0) break;
        addr += (16 * 1024);
    }
    nand_deselect();
    return 0;
}

void do_erase_nand_flash(void) {
    uint32 addr;
    printf("Enter the address of sector to erase:");
    addr = get_uint();

    printf("erasing... \r\n");
    nand_erase(addr, 16 * 1024);
}

void nand_write(uint32 addr, uint8 *buf, uint32 len) {
    uint32 col = addr & (NAND_PAGE_SIZE - 1);
    uint32 page = addr / NAND_PAGE_SIZE;
    int i = 0;

    nand_select();
    printf("write col:0x%x, page:0x%x\r\n", col, page);

    while (1) {
        nand_cmd(CMD_WRITE1);
        nand_send_addr(col, page);

        /* 发出数据 */
        for (; (col < 512) && (i < len);) {
            nand_w_data(buf[i++]);
        }
        nand_cmd(CMD_WRITE2);
        wait_ready();

        nand_cmd(CMD_STATUS);
        if (NFDATA & 0x01) {
            printf("write failed!\r\n");
        } else {
            printf("write success!\r\n");
        }

        if (i == len)
            break;
        else {
            /* 开始下一个循环page */
            col = 0;
            page++;
        }
    }
    nand_deselect();
}

void do_write_nand_flash(void) {
    uint32 addr;
    uint8 str[100];
    int i, j;
    uint32 val;

    /* 获得地址 */
    printf("Enter the address of sector to write: ");
    addr = get_uint();

    printf("Enter the string to write: ");
    gets(str);

    printf("writing ...%s, len:%d\n\r", str, strlen(str) + 1);
    nand_write(addr, str, strlen(str) + 1);
}

void do_check_bad_block_nand_flash() {
    uint32 addr;

    /* 获得地址 */
    printf("Enter the address of sector to check: ");
    addr = get_uint();

    printf("checking ...\n\r");
    nand_select();
    nand_check_bad_block(addr / NAND_BLOCK_SIZE);
    nand_deselect();
}

void nand_chip_id(void) {
    uint8 buf[5] = {0};
    nand_select();
    nand_cmd(0x90);
    nand_addr_byte(0x00);
    buf[0] = nand_data();
    buf[1] = nand_data();
    buf[2] = nand_data();
    buf[3] = nand_data();
    buf[4] = nand_data();
    nand_deselect();

    printf("maker  id  =0x%x\r\n", buf[0]);
    printf("device id  =0x%x\r\n", buf[1]);
    printf("3rd byte   =0x%x\r\n", buf[2]);
    printf("4th byte   =0x%x\r\n", buf[3]);
    printf("5th byte   =0x%x\r\n", buf[4]);
}

void nand_flash_test(void) {
    char c;

    while (1) {
        puts("[s] Scan nand flash\r\n");
        puts("[e] Erase nand flash\r\n");
        puts("[w] Write nand flash\r\n");
        puts("[r] Read nand flash\r\n");
        puts("[c] check bad block\r\n");
        puts("[q] quit\r\n");
        puts("Enter selection: ");

        c = getchar();
        printf("%c\n\r", c);

        switch (c) {
            case 'q':
            case 'Q':
                return;
                break;
            case 's':
            case 'S':
                nand_chip_id();
                break;
            case 'e':
            case 'E':
                do_erase_nand_flash();
                break;
            case 'w':
            case 'W':
                do_write_nand_flash();
                break;
            case 'r':
            case 'R':
                do_read_nand_flash();
                break;
            case 'c':
            case 'C':
                do_check_bad_block_nand_flash();
                break;
        }
    }
}
